package me.zhenqu.redis;

import me.zhenqu.redis.utils.CollectionUtils;
import me.zhenqu.redis.utils.ReflectUtils;
import me.zhenqu.redis.utils.Str2LongTransformer;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.type.TypeReference;
import org.joda.time.DateTime;
import redis.clients.util.SafeEncoder;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.*;
import static me.zhenqu.redis.JSONSerializer.JSON;

public class RedisCache {
    private String name;
    private int ttlSeconds = 3600;
    private RedisOperator operator = RedisOperator.getCacheInstance();

    public RedisCache(String name) {
        this.name = name;
    }

    public RedisCache(String name, int ttlSeconds) {
        this.name = name;
        this.ttlSeconds = ttlSeconds;
    }

    public void put(String key, String value) {
        operator.setEx(name, key, value, ttlSeconds);
    }

    public void put(String key, long value) {
        operator.setEx(name, key, Long.toString(value), ttlSeconds);
    }

    public void put(String key, boolean value) {
        operator.setEx(name, key, value ? "1" : "0", ttlSeconds);
    }

    public void put(String key, DateTime value) {
        put(key, value.getMillis());
    }

    public void put(String key, Date value) {
        put(key, value.getTime());
    }

    public void put(String key, Number value) {
        if (value != null) {
            put(key, value.toString());
        } else {
            put(key, (String) null);
        }
    }

    public void put(String key, byte[] value) {
        if (value == null) {
            put(key, (String) null);
        } else {
            put(key, SafeEncoder.encode(value));
        }
    }

    public byte[] getBytes(String key) {
        String string = getString(key);
        if (string == null) return null;
        return SafeEncoder.encode(string);
    }

    public void put(String key, Object value) {
        if (value == null) {
            operator.removeCategory(name, key);
        } else {
            Map<String, Object> describe = ReflectUtils.describe(value);
            Map<String, String> values = new HashMap<String, String>();
            for (Map.Entry<String, Object> entry : describe.entrySet()) {
                if (!"partitionKey".equals(entry.getKey()) && entry.getValue() != null) {
                    Object v = entry.getValue();
                    if (entry.getValue() instanceof String) {
                        values.put(entry.getKey(), (String) v);
                    } else if (Number.class.isAssignableFrom(v.getClass())) {
                        values.put(entry.getKey(), String.valueOf(v));
                    } else if (Boolean.class == v.getClass()) {
                        values.put(entry.getKey(), (Boolean) v ? "1" : "0");
                    } else if (Date.class.isAssignableFrom(v.getClass())) {
                        values.put(entry.getKey(), String.valueOf(((Date) v).getTime()));
                    } else if (DateTime.class.isAssignableFrom(v.getClass())) {
                        values.put(entry.getKey(), String.valueOf(((DateTime) v).getMillis()));
                    } else if (v.getClass().isPrimitive()) {
                        if (v.getClass() == Boolean.TYPE) {
                            values.put(entry.getKey(), (Boolean) v ? "1" : "0");
                        } else {
                            values.put(entry.getKey(), String.valueOf(v));
                        }
                    } else {
                        try {
                            values.put(entry.getKey(), JSON.writeValueAsString(v));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            operator.setFields(name, key, values);
            operator.setExpire(name, ttlSeconds);
        }
    }

    public void putJson(String key, Object o) {
        try {
            put(key, JSON.writeValueAsString(o));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public <T> List<T> mgetJson(List<String> keys, Class<T> c) {
        List<String> strings = operator.multiGet(name, keys);
        List<T> ret = new ArrayList<T>(keys.size());
        for (String string : strings) {
            T t = null;
            try {
                t = StringUtils.isEmpty(string) ? null : JSON.readValue(string, c);
            } catch (IOException e) {
                e.printStackTrace();
            }
            ret.add(t);
        }
        return ret;
    }

    public List<String> mgetString(List<String> keys) {
        return operator.multiGet(name, keys);
    }

    public List<Long> mgetLong(List<String> keys) {
        List<String> strings = operator.multiGet(name, keys);
        return CollectionUtils.collect(strings, Str2LongTransformer.INSTANCE);
    }

    public void touch(String key) {
        operator.setExpire(name, key, ttlSeconds);
    }

    public <T> T getJson(String key, Class<T> c) {
        String s = getString(key);
        if (StringUtils.isEmpty(s)) {
            return null;
        }
        try {
            return JSON.readValue(s, c);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public <T> T getJsonVal(String key, TypeReference c) {
        String s = getString(key);
        if (StringUtils.isEmpty(s)) {
            return null;
        }
        try {
            return (T) JSON.readValue(s, c);
        } catch (IOException e) {
            return null;
        }
    }

    public <T> List<T> getJsonList(String key, Class<T> c) {
        String s = getString(key);
        if (StringUtils.isEmpty(s)) {
            return null;
        }
        try {
            return JSON.readValue(s, new TypeReference<List<T>>() {
            });
        } catch (IOException e) {
            return null;
        }
    }

    private String objectToString(Object v) {
        if (v == null) return null;
        if (v instanceof String) return (String) v;
        if (v instanceof Number) return String.valueOf(v);
        if (v instanceof Boolean) return (Boolean) v ? "1" : "0";
        if (v instanceof Date) return String.valueOf(((Date) v).getTime());
        if (v instanceof DateTime) return String.valueOf(((DateTime) v).getMillis());
        if (v.getClass().isPrimitive()) return String.valueOf(v);
        try {
            return JSON.writeValueAsString(v);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void set(String key, String property, String value) {
        operator.setField(name, key, property, value);
    }

    public String get(String key, String property) {
        return operator.getField(name, key, property);
    }

    public String getString(String key) {
        return operator.getString(name, key);
    }

    public Long getLong(String key) {
        return operator.getLong(key);
    }

    public Date getDate(String key) {
        Long aLong = operator.getLong(name, key);
        return aLong == null ? null : new Date(aLong);
    }

    public DateTime getDateTime(String key) {
        Long aLong = operator.getLong(name, key);
        return aLong == null ? null : new DateTime(aLong);
    }

    public void append(String key, Collection<?> data) {
        Collection<String> s = new ArrayList<String>();
        for (Object o : data) {
            operator.appendToList(name, key, objectToString(o));
        }
    }

    public void put(String key, Collection<?> data) {
        operator.removeCategory(name, key);
        for (Object o : data) {
            operator.appendToList(name, key, objectToString(o));
        }
        operator.setExpire(name, key, ttlSeconds);
    }

    public <T> List<T> getList(String key, Class<T> t) {
        List<T> ret = new ArrayList<T>();
        long size = operator.size(name, key);
        List<String> stringList = operator.subList(name, key, 0, (int) size);
        for (String value : stringList) {
            if (t == String.class) {
                ret.add((T) value);
            } else if (Number.class.isAssignableFrom(t)) {
                try {
                    ret.add(t.getConstructor(String.class).newInstance(value));
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            } else if (t.isPrimitive() && Boolean.TYPE == t || Boolean.class == t) {
                ret.add((T) ((Boolean) "1".equals(value)));
            } else if (Date.class.isAssignableFrom(t)) {
                ret.add((T) new Date(Long.parseLong(value)));
            } else if (DateTime.class.isAssignableFrom(t)) {
                ret.add((T) new DateTime(Long.parseLong(value)));
            } else {
                ret.add(parseObject(t, value));
            }
        }
        return ret;
    }

    public <T> T get(String key, Class<T> type) {
        Map<String, String> values;
        try {
            values = operator.getAllFieldValues(name, key);
        } catch (Exception e) {
            operator.removeCategory(name, key);
            return null;
        }
        try {
            if (values.size() == 0) return null;
            Map<String, Class<?>> types = ReflectUtils.getTypes(type);
            T vo = type.newInstance();
            for (Map.Entry<String, String> entry : values.entrySet()) {
                String k = entry.getKey();
                Class<?> t = types.get(k);
                if (!types.containsKey(k)) continue;
                String value = entry.getValue();
                if (t == String.class || Number.class.isAssignableFrom(t)) {
                    ReflectUtils.setProperty(vo, k, value);
                } else if (t.isPrimitive() && Boolean.TYPE == t || Boolean.class == t) {
                    ReflectUtils.setProperty(vo, k, "1".equals(value));
                } else if (Date.class.isAssignableFrom(t)) {
                    ReflectUtils.setProperty(vo, k, new Date(Long.parseLong(value)));
                } else if (DateTime.class.isAssignableFrom(t)) {
                    ReflectUtils.setProperty(vo, k, new DateTime(Long.parseLong(value)));
                } else if (t.isInterface() || Modifier.isAbstract(t.getModifiers())) {
                    //ignore
                } else {
                    ReflectUtils.setProperty(vo, k, parseObject(t, value));
                }
            }
            return vo;
        } catch (InstantiationException e) {
            throw new IllegalArgumentException("没有默认构造方法");
        } catch (IllegalAccessException e) {
            throw new IllegalArgumentException("没有默认构造方法");
        }
    }

    private <T> T parseObject(Class<T> t, String value) {
        try {
            return JSON.readValue(value, t);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public boolean isCached(String key) {
        try {
            return operator.exists(name, key);
        } catch (ClassCastException e) {
            return true;
        }
    }

    public void remove(String key) {
        operator.removeCategory(name, key);
    }

    public <T> void add(String key, Set<T> set) {
        operator.removeCategory(name, key);
        for (Object o : set) {
            operator.add(name, key, objectToString(o));
        }
        operator.setExpire(name, key, ttlSeconds);
    }

    public void remove(String key, Object o) {
        operator.removeValue(name, key, objectToString(o));
    }

    public long getSetSize(String key) {
        return operator.getSetSize(name, key);
    }

    public <T> Set<T> getSet(String key, Class<T> t) {
        Set<T> ret = new HashSet<T>();
//        long size = operator.size(name, key);
        Set<String> stringList = operator.getSet(name, key);
        for (String value : stringList) {
            if (t == String.class) {
                ret.add((T) value);
            } else if (Number.class.isAssignableFrom(t)) {
                try {
                    ret.add(t.getConstructor(String.class).newInstance(value));
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            } else if (t.isPrimitive() && Boolean.TYPE == t || Boolean.class == t) {
                ret.add((T) (Boolean) ("1".equals(value)));
            } else if (Date.class.isAssignableFrom(t)) {
                ret.add((T) new Date(Long.parseLong(value)));
            } else if (DateTime.class.isAssignableFrom(t)) {
                ret.add((T) new DateTime(Long.parseLong(value)));
            } else {
                ret.add(parseObject(t, value));
            }
        }
        return ret;
    }
}
